package cn.tml.innermost.framework.security.token;


import cn.tml.innermost.framework.cache.impl.RedisCache;
import cn.tml.innermost.framework.entity.enums.ResultCode;
import cn.tml.innermost.framework.exception.ServiceException;
import cn.tml.innermost.framework.properties.JWTTokenProperties;
import cn.tml.innermost.framework.security.AuthUser;
import cn.tml.innermost.framework.security.context.UserContext;
import cn.tml.innermost.framework.security.enums.SecurityEnum;
import cn.tml.innermost.framework.utils.RedisKeyUtil;
import com.google.gson.Gson;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SignatureException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * TokenUtil
 */
@Component
public class TokenUtil {
    private final JWTTokenProperties tokenProperties;
    private final RedisCache cache;

    @Autowired
    public TokenUtil(JWTTokenProperties tokenProperties, RedisCache cache) {
        this.tokenProperties = tokenProperties;
        this.cache = cache;
    }

    /**
     * 用于用户登陆的时候创建token
     *
     * @param authUser 鉴权用户
     * @return token
     */
    public Token createToken(AuthUser authUser) {
        String uuid = UserContext.getCurrentUserUUID();
        if(uuid==null)throw new ServiceException(ResultCode.UUID_NOT_FIND);
        String loginKey = RedisKeyUtil.loginKey(authUser);
        //获取是否长期有效的token
        boolean longTerm = authUser.getLongTerm();
        //访问token
        String accessToken = createToken(authUser, tokenProperties.getTokenExpireTime());
        //将用户的登录设备存入缓存
        cache.put(loginKey, uuid, tokenProperties.getTokenExpireTime(), TimeUnit.HOURS);
        //刷新token生成策略：如果是长时间有效的token（用于app），则默认15天有效期刷新token。如果是普通用户登录，则刷新token为普通token2倍数
//        Long expireTime = longTerm ? 15 * 24 * 60L : tokenProperties.getTokenExpireTime() * 2;
        Long expireTime = 24L;
        String refreshToken = createToken(authUser, expireTime);

        Token token = new Token();
        token.setAccessToken(accessToken);
        token.setRefreshToken(refreshToken);
        return token;
    }

    /**
     * 要是用户的刷新token还没有过期，就重新给用户生成新的token，否则抛出登录过期异常
     *
     * @param oldRefreshToken 刷新token
     * @return token
     */
    public Token refreshToken(String oldRefreshToken) {
        AuthUser authUser = parseToken(oldRefreshToken);
        if (authUser == null) throw new ServiceException(ResultCode.USER_AUTH_EXPIRED);
        return createToken(authUser);
    }

    /**
     * 生成token
     *
     * @param authUser       鉴权用户
     * @param expirationTime 过期时间（分钟）
     * @return token字符串
     */
    private String createToken(AuthUser authUser, Long expirationTime) {
        //JWT 生成
        return Jwts.builder()
                //jwt 私有声明
                .claim(SecurityEnum.USER_CONTEXT.getValue(), new Gson().toJson(authUser))
                //JWT的主体
                .setSubject(authUser.getUsername())
                //失效时间 当前时间+过期分钟
                .setExpiration(new Date(System.currentTimeMillis() + expirationTime * 60 * 60 * 1000))
                //签名算法和密钥
                .signWith(SecretKeyUtil.generalKey())
                .compact();
    }

    /**
     * 从token中解析出鉴权用户，失败则返回NULL
     *
     * @param token token字符串
     * @return 鉴权用户 | NULL
     */
    public AuthUser parseToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(SecretKeyUtil.generalKeyByDecoders())
                    .parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException |
                 IllegalArgumentException e) {
            //token 过期 认证失败等
            return null;
        }

        //获取存储在claims中的用户信息
        String json = claims.get(SecurityEnum.USER_CONTEXT.getValue()).toString();
        return new Gson().fromJson(json, AuthUser.class);
    }
}
